// 加强版防守插件
/* 本插件无需更改VPK脚本，直接使用即可，并且不操作原地图生成，所有僵尸量为增加值 */

string author = "Live";
string pluginName = "FlatlinePro 加强防守插件";
string version = "1.0";

//------------------------------全局变量------------------------------

// 普通僵尸常量
int ZOMBIEC = 0;

// 防爆僵尸常量
int RIOTC = 0;

// 监狱男BOSS常量
int CARRIERC = 0;

// 盾牌哥常量
int CHARGERC = 0;

// 尖叫僵尸常量
int SCREAMERC = 0;

// 波启动？
bool Wave = false;

//------------------------------插件初始化------------------------------
// 插件初始化
void OnPluginInit()
{
    // 设置插件信息
    PluginData::SetAuthor(author);
    PluginData::SetName(pluginName);
    PluginData::SetVersion(version);

    Engine.RunConsoleCommand( "mapcyclefile onlyFlatLine.txt");

    // 加载后提示信息
    ThePresident.InfoFeed("插件:"+pluginName+"| 版本:"+version+"| 作者:"+author,false);

    // 防卫模式波开始
    Events::FlatLine::WaveStart.Hook( @WaveStart );
    // 波结束
    Events::FlatLine::WaveEnded.Hook( @WaveEnded );
    // 当回合终止时
    Events::ThePresident::OnTerminateRound.Hook( @OnTerminateRound );
    // 当僵尸出生后
    Events::Infected::OnInfectedSpawned.Hook( @OnInfectedSpawned );
    // 感染者死亡
    Events::Infected::OnInfectedKilled.Hook( @OnInfectedKilled );
    // 玩家说
    Events::Player::PlayerSay.Hook( @PlayerSay );
    // 执行菜单项
    Events::Player::OnMenuExecuted.Hook( @OnMenuExecuted );
    // 实体生成
    Events::Entities::OnEntityCreation.Hook( @OnEntityCreation );
    // 玩家即将受到伤害
    Events::Player::OnPlayerDamagedPre.Hook( @OnPlayerDamagedPre );
    // 感染者即将受到伤害
    Events::Infected::OnInfectedDamagedPre.Hook( @OnInfectedDamagedPre );

    // 地图开始 
    ThePresident_OnMapStart();
}

//------------------------------回合控制------------------------------

// 地图开始
void ThePresident_OnMapStart(){

    // 服务器中文名
    Engine.RunConsoleCommand( "hostname [CN.Live] 加强版防守模式 FlatlinePro QQ群392635015");
    // 设置难度
    Engine.RunConsoleCommand( "Difficulty 4" );
    // 设置无法投票更改难度
    Engine.RunConsoleCommand( "sv_vote_issue_change_difficulty_allowed 0" );
    // 设置普通僵尸伤害量（ 0 普通僵尸普通的伤害 1 普通僵尸伤害与逃生一致）
	Engine.RunConsoleCommand( "cf_zombie_dealsamedamage 3" );
    // 设置防守BOSS( 0 无boss 1 中场末场boss 2 仅末场boss 3 第5波开始boss当小怪刷)
	Engine.RunConsoleCommand( "cf_enable_bosses 0" );
	// 设置僵尸生命值（ 0 不变 1 波数 2 人数 ）
	Engine.RunConsoleCommand( "cf_zombie_healthincrease_type 2" );

    // 执行回合开始
    ThePresident_OnRoundStart();
}

// 回合开始
void ThePresident_OnRoundStart(){
	CASConVarRef@ mounted_gun_overheat_time = ConVar::Find( "mounted_gun_overheat_time" );
	mounted_gun_overheat_time.SetValue("180");
    // npc无限子弹
    npcInfiniteAmmo();

    //起始资金（实测貌似只有第一次有效）
    ThePresident::Flatline::StartCash(1000);
    //自动保存
    ThePresident::Flatline::AutoSaveWave(10);
}


/*************************************************************************

                                    HOOK

**************************************************************************/

//------------------------------波开始------------------------------

// 波开始
HookReturnCode WaveStart(int &in iRound)
{
    // 测试
    // Engine.RunConsoleCommand( "sv_cheats 1" );
    // Engine.RunConsoleCommand( "god 1" );
    // Engine.RunConsoleCommand( "developer 1" );

    // 设置无限体力(因为游戏会默认关闭，所以需要延迟)
    Schedule::Task(2,infiniteStamina);

    // 设置本关的僵尸常量
    ZOMBIEC = 20 + ( iRound - 2 ) * 5;
    RIOTC = 2 + iRound * 1;
    CARRIERC = 0 + ( iRound -3 ) * 5;
    CHARGERC = 0 + ( iRound - 4 ) * 3;
    SCREAMERC = 0 + ( iRound - 8 ) * 2;

    Wave = true;
    
    infiniteZombie();

    ThePresident::Flatline::OpenShopItems(true);
    ThePresident.BeginHordeMode();

    return HOOK_HANDLED;
}

void infiniteStamina(){
	CASConVarRef@ infiniteStamina = ConVar::Find( "sv_infinite_stamina" );
	infiniteStamina.SetValue("1");
	Chat.PrintToChat(all,"无限体力已启用");
}

//------------------------------波结束------------------------------

// 波结束
HookReturnCode WaveEnded(bool &in bCompleted)
{
    Wave = false;
	return HOOK_CONTINUE;
}

//------------------------------回合终止------------------------------

// 回合终止时
HookReturnCode OnTerminateRound(int iTeam)
{
    Wave = false;
    return HOOK_CONTINUE;
}

//------------------------------僵尸出生------------------------------

//僵尸出生后
HookReturnCode OnInfectedSpawned(Infected@ pInfected)   
{
    return HOOK_CONTINUE;
}

//------------------------------僵尸死亡------------------------------

// 僵尸死亡后
HookReturnCode OnInfectedKilled(Infected@ pInfected, CTakeDamageInfo &in DamageInfo)
{
    // 用来清除掉落的升级配件，防止太多卡顿
    if( pInfected.GetClassname() == "riotcharger" || pInfected.GetClassname() == "carrier"){
        if( SelectEntNumByClass("item_upgrademodule") > 15 ){
            CBaseEntity@ base = FindEntityByClassname("item_upgrademodule");
            base.SUB_StartFadeOut(2.0f,false);
            Chat.PrintToChat(all,"{hotpink}地上的升级配件过多，产出已自动清除，请及时拾取");
        }
    }

    // 给玩家额外加钱
    if(Utils.GetNumPlayers(true)>0){
        array<int> players = Utils.CollectPlayers();
        for(uint i =0;i<players.length();i++){
            CBaseEntity@ cPlayer = FindEntityByEntIndex(players[i]);
            NetProp::SetPropInt(cPlayer,"m_iAmountOfDosh",(NetProp::GetPropInt(cPlayer,"m_iAmountOfDosh")+Math::RandomInt(1,100)));
        }
    }
    return HOOK_CONTINUE;
}

//------------------------------实体创建------------------------------

HookReturnCode OnEntityCreation(const string &in strClassname, CBaseEntity@ pEntity)
{
    //Chat.PrintToChat(all,strClassname);
    if( strClassname == "survivor_turret" ){
        Engine.Ent_Fire( "survivor_turret", "SetAllowMoveable" );
    }else if( strClassname == "item_ammo_arrow" ){
        if( SelectEntNumByClass("item_ammo_arrow") > 15 )
            pEntity.SUB_StartFadeOut(1.0f,false);
    }
	return HOOK_CONTINUE;
}

//------------------------------玩家即将受到伤害------------------------------

HookReturnCode OnPlayerDamagedPre(CTerrorPlayer@ pPlayer, CTakeDamageInfo &in DamageInfo)
{
	CBaseEntity@ cWeapon = DamageInfo.GetWeapon();
	if( cWeapon.ClassnameIs("weapon_grenadelauncher")){
		DamageInfo.SetDamage(0);
	}
	return HOOK_CONTINUE;
}

//------------------------------丧尸即将受到伤害------------------------------

HookReturnCode OnInfectedDamagedPre(Infected@ pInfected, CTakeDamageInfo &in DamageInfo)
{
    // 将炮塔伤害增加10倍
    CBaseEntity@ base = DamageInfo.GetInflictor();
    if( base.GetClassname() == "survivor_turret" ){
        DamageInfo.SetDamage(DamageInfo.GetDamage() * 8);
    }
	return HOOK_CONTINUE;
}

/*************************************************************************

                                    玩家命令

**************************************************************************/

HookReturnCode PlayerSay(CTerrorPlayer@ pPlayer, CASCommand@ pArgs)
{

    //------------------------------字符串切分处理------------------------------

    string arg = pArgs.Arg( 1 );
	string arg1 = "";
	string arg2 = "";

	// 切分字符串(这样就可以用空格传一个命令参数了)
	int subIndex = arg.findFirst(" ",0);
	if(not (subIndex < 0)){
		arg1 = arg.substr(0,subIndex);
		arg2 = arg.substr(subIndex+1,-1);
		//Chat.PrintToChat(all,""+arg1+"|"+arg2);
	}else{
		arg1 = arg;
	}

    //------------------------------命令------------------------------

    if(AdminSystem.AdminExist(pPlayer)){
        if( arg1 == "!m" ){
            NetProp::SetPropInt(FindEntityByEntIndex(pPlayer.entindex()),"m_iAmountOfDosh",18000);
        }else if( arg1 == "!give" ){
            pPlayer.GiveWeapon(arg2);
        }else if( arg1 == "!start" ){
            // 设置本关的僵尸常量
            Wave = true;
            Chat.PrintToChat(pPlayer,"手动启动波成功");
        }else if( arg1 == "!god" ){
            if( arg2!="" ){
                if( arg2 == "0" ){
                    pPlayer.SetGodMode(false);
                    Chat.PrintToChat(pPlayer,"无敌关闭");
                }else if( arg2 == "1" )
                    pPlayer.SetGodMode(true);
                    Chat.PrintToChat(pPlayer,"无敌开启");
            }
        }else if( Utils.StrEql( arg1, "!fh" ) ){
			// 得是管理员
			if( AdminSystem.AdminExist(pPlayer) ){
				if( not Utils.StrEql( arg2, "" ) ){
					Vector v = pPlayer.GetAbsOrigin();
					if( arg2 == "all" ){
						array<int> playerList = Utils.CollectPlayers();
						for(uint i=0;i<playerList.length();i++){
							CTerrorPlayer@ player = ToTerrorPlayer(playerList[i]);
							if(not (player.GetTeamNumber() == TEAM_SURVIVOR)){
								// 修改被复活玩家的游戏阵营
								player.ChangeTeam( TEAM_SURVIVOR );
								// 复活玩家
								player.Respawn();
								player.SetAbsOrigin(v);
							}
						}
						Chat.PrintToChat(all,"管理员复活了所有玩家");
					}else{
						CTerrorPlayer@ splayer = ToTerrorPlayer( parseInt(arg2,10,0) );
						if(splayer is null) return HOOK_CONTINUE;
						// 修改被复活玩家的游戏阵营
						splayer.ChangeTeam( TEAM_SURVIVOR );
						// 复活玩家
						splayer.Respawn();
						splayer.SetAbsOrigin(v);
						Chat.PrintToChat(all,"{hotpink}管理员使用命令复活了["+splayer.GetPlayerName()+"]");
					}
				}else{
					// 修改被复活玩家的游戏阵营
					pPlayer.ChangeTeam( TEAM_SURVIVOR );
					// 复活玩家
					pPlayer.Respawn();
				}
			}else{
				Chat.PrintToChat(pPlayer,"您不是管理员，无法执行复活命令");
			}
		}
    }
    if( arg1 == "!b" || arg1 == "!buy" ){
        SHOP(pPlayer);
    }

    return HOOK_CONTINUE;

}

/*************************************************************************

                                    商店

**************************************************************************/

//------------------------------菜单------------------------------

void SHOP(CTerrorPlayer@ pPlayer){

    Menu pMenu;
    pMenu.SetID("SHOP");
    pMenu.SetTitle("@商店@");
    pMenu.AddItem("生成弹药箱(2000)");
    pMenu.AddItem("生成自动炮塔(4500)");
    pMenu.AddItem("生成幸存者(750)");
    pMenu.AddItem("生成神秘箱(1000)");
    pMenu.AddItem("生成回收箱(1500)");
	pMenu.AddItem("生成固定机枪(8000)");
    pMenu.Display(pPlayer,-1);
    Chat.HintMessagePlayer(pPlayer, "局内金钱:"+NetProp::GetPropInt(FindEntityByEntIndex(pPlayer.entindex()),"m_iAmountOfDosh"), Color("#ffffff"));

}

HookReturnCode OnMenuExecuted(CTerrorPlayer@ pPlayer, const string &in szID, int &in iValue)
{
    if( szID == "SHOP" ){
        Vector ve = pPlayer.GetAbsOrigin();
        QAngle qa = pPlayer.GetAbsAngles();
        int money = NetProp::GetPropInt(FindEntityByEntIndex(pPlayer.entindex()),"m_iAmountOfDosh");
        CBaseEntity@ cPlayer = FindEntityByEntIndex(pPlayer.entindex());
        switch( iValue ){
            case 1:{
                int price = 2000;
                if( money >= price ){
                    CEntityData@ inputdata = EntityCreator::EntityData();
                    inputdata.Add("targetname","ammoBox");
                    inputdata.Add("shopid","19");
                    inputdata.Add("model","models/props_downtown/ammo_crate.mdl");
                    CBaseEntity@ ammoBOX = EntityCreator::Create("shop_item",ve,qa,inputdata);
                    Engine.Ent_Fire( "ammoBox", "Enable" );
                    Chat.PrintToChat(pPlayer,"原地生成了一个弹药箱");
                    NetProp::SetPropInt(cPlayer,"m_iAmountOfDosh",money-price);
                }else{
                    Chat.HintMessagePlayer(pPlayer,"金币不够",Color("#ffffff"));
                }
                break;
            }
            case 2:{
                int price = 4500;
                //Chat.PrintToChat(all,""+SelectEntNumByClass("survivor_turret"));
                if( SelectEntNumByClass("survivor_turret") < 10 ){
                    if( money >= price ){
                        CEntityData@ inputdata = EntityCreator::EntityData();
                        inputdata.Add("targetname","button");
                        inputdata.Add("shopid","25");
                        inputdata.Add("model","models/props_rpd/guard_button02.mdl");
                        CBaseEntity@ ammoBOX = EntityCreator::Create("shop_item",ve,qa,inputdata);
                        Engine.Ent_Fire( "button", "Enable" );
                        Chat.PrintToChat(pPlayer,"原地生成了一个自动炮塔");
                        NetProp::SetPropInt(cPlayer,"m_iAmountOfDosh",money-price);
                    }else{
                        Chat.HintMessagePlayer(pPlayer,"金币不够",Color("#ffffff"));
                    }
                }else{
                    Chat.HintMessagePlayer(pPlayer,"已达最大炮塔数（多了会卡）",Color("#ffffff"));
                }
                break;
            }
            case 3:{
                int price = 750;
                if( SelectEntNumByClass("survivor") + SelectEntNumByClass("survivor_riot") < 5 ){
                    if( money >= price ){
                        CEntityData@ inputdata = EntityCreator::EntityData();
                        CBaseEntity@ turret = EntityCreator::Create("survivor_riot",ve,qa,inputdata);
                        Chat.PrintToChat(pPlayer,"原地生成了一个幸存者");
                        NetProp::SetPropInt(cPlayer,"m_iAmountOfDosh",money-price);
                    }else{
                        Chat.HintMessagePlayer(pPlayer,"金币不够",Color("#ffffff"));
                    }
                }else{
                    Chat.HintMessagePlayer(pPlayer,"已达最大人机数（多了会卡）",Color("#ffffff"));
                }
                break;
            }
            case 4:{
                int price = 1000;
                if( money >= price ){
                    CEntityData@ inputdata = EntityCreator::EntityData();
                    inputdata.Add("targetname","mysterybox");
                    inputdata.Add("shopid","20");
                    inputdata.Add("model","models/props_flatline/mysterybox.mdl");
                    CBaseEntity@ ammoBOX = EntityCreator::Create("shop_item",ve,qa,inputdata);
                    Engine.Ent_Fire( "mysterybox", "Enable" );
                    Chat.PrintToChat(pPlayer,"原地生成了一个神秘箱");
                    NetProp::SetPropInt(cPlayer,"m_iAmountOfDosh",money-price);
                }else{
                    Chat.HintMessagePlayer(pPlayer,"金币不够",Color("#ffffff"));
                }
                break;
            }
            case 5:{
                int price = 1500;
                if( money >= price ){
                    CEntityData@ inputdata = EntityCreator::EntityData();
                    inputdata.Add("targetname","garbage");
                    inputdata.Add("shopid","26");
                    inputdata.Add("model","models/props_biotec/garbage_cart_01.mdl");
                    CBaseEntity@ ammoBOX = EntityCreator::Create("shop_item",ve,qa,inputdata);
                    Engine.Ent_Fire( "garbage", "Enable" );
                    Chat.PrintToChat(pPlayer,"原地生成了一个回收箱");
                    NetProp::SetPropInt(cPlayer,"m_iAmountOfDosh",money-price);
                }else{
                    Chat.HintMessagePlayer(pPlayer,"金币不够",Color("#ffffff"));
                }
                break;
            }
			case 6:{
				int price = 8000;
                if( money >= price ){
                    CEntityData@ inputdata = EntityCreator::EntityData();
					// 经过实测 MaxPitch MinPitch 分别为最高角度/最低角度 其极限值为大约-60度~60度之间
					// MaxYaw 为最大左右旋转角度，其极限值大约为180度
					// 但是以下都设置为360度是因为当取消机枪碰撞模型时，按E很难触发，增大值能更容易触发
					inputdata.Add("MaxPitch","360.00");
					inputdata.Add("MinPitch","-360.00");
					inputdata.Add("MaxYaw","360.00");
					CBaseEntity@ mounted_gun = EntityCreator::Create("prop_mounted_machine_gun",ve,qa,inputdata);
					mounted_gun.SetSolidType(SOLID_NONE);
					Chat.PrintToChat(pPlayer,"原地生成了一个固定机枪");
                    NetProp::SetPropInt(cPlayer,"m_iAmountOfDosh",money-price);
                }else{
                    Chat.HintMessagePlayer(pPlayer,"金币不够",Color("#ffffff"));
                }
				break;
			}
        }
    }
    // if( iValue != 0 ){
    //     SHOP(pPlayer);
    // }
    return HOOK_CONTINUE;

}



/*************************************************************************

                                    核心方法

**************************************************************************/

//------------------------------无限刷僵尸------------------------------
void infiniteZombie(){

    // 写在最前面，防止各种意外情况导致方法停止
    Schedule::Task( 0.5,infiniteZombie );

    if( Wave ){

        Log.PrintToServerConsole(LOGTYPE_INFO,"-------------check----------------");

        string name = "trigger_zombie_spawns";
        CBaseEntity @pEnt = FindEntityByClassname( null, name );
        while( pEnt !is null && Utils.CollectInfected().length() <= 60){

            // 刷普通僵尸
            int ZOMBIE = SelectEntNumByName("my_zombie");
            Log.PrintToServerConsole(LOGTYPE_INFO,"-------ZOMBIE-------"+ZOMBIE+"-------");
            // 僵尸太多容易卡
            if( ZOMBIE < ZOMBIEC && Utils.CollectInfected().length() < 60){
                CBaseEntity@ entZOMBIE = EntityCreator::Create("zombie", pEnt.GetAbsOrigin() , QAngle(0, 0, 0));
                entZOMBIE.SetEntityName("my_zombie");
            }

            // 刷防爆僵尸
            int RIOT = SelectEntNumByName("my_riot");
            Log.PrintToServerConsole(LOGTYPE_INFO,"-------RIOT-------"+RIOT+"-------");
            if( RIOT < RIOTC ){
                CBaseEntity@ entRIOT = EntityCreator::Create("zombieriot", pEnt.GetAbsOrigin() , QAngle(0, 0, 0));
                entRIOT.SetEntityName("my_riot");
            }

            // 刷监狱男BOSS
            int CARRIER =  SelectEntNumByName("my_carrier");
            Log.PrintToServerConsole(LOGTYPE_INFO,"-------CARRIER-------"+CARRIER+"-------");
            if( CARRIER < CARRIERC ){
                CBaseEntity@ entCARRIER = EntityCreator::Create("carrier", pEnt.GetAbsOrigin() , QAngle(0, 0, 0));
                entCARRIER.SetEntityName("my_carrier");
            }

            // 刷盾牌BOSS
            int CHARGER = SelectEntNumByName("my_riotcharger");
            Log.PrintToServerConsole(LOGTYPE_INFO,"-------CHARGER-------"+CHARGER+"-------");
            if( CHARGER < CHARGERC ){
                CBaseEntity@ entCHARGER = EntityCreator::Create("riotcharger", pEnt.GetAbsOrigin() , QAngle(0, 0, 0));
                entCHARGER.SetEntityName("my_riotcharger");
            }

            // 刷尖叫僵尸
            int SCREAMER = SelectEntNumByName("my_screamer");
            Log.PrintToServerConsole(LOGTYPE_INFO,"-------SCREAMER-------"+SCREAMER+"-------");
            if( SCREAMER < SCREAMERC ){
                CBaseEntity@ entSCREAMER = EntityCreator::Create("screamer", pEnt.GetAbsOrigin() , QAngle(0, 0, 0));
                entSCREAMER.SetEntityName("my_screamer");
            }

            @pEnt = FindEntityByClassname( pEnt, name );
        }
    }

}


/*************************************************************************

                                    工具方法

**************************************************************************/

//------------------------------查询实体数量通过Class------------------------------

int SelectEntNumByClass(string name){

    int num = 0;

    CBaseEntity @pEnt = FindEntityByClassname( null, name );
    while( pEnt !is null ){
        num++;
        @pEnt = FindEntityByClassname( pEnt, name );
    }
    return num;

}

//------------------------------查询实体数量通过Name------------------------------

int SelectEntNumByName(string name){

    int num = 0;

    CBaseEntity @pEnt = FindEntityByName( null, name );
    while( pEnt !is null ){
        num++;
        @pEnt = FindEntityByName( pEnt, name );
    }
    return num;
}

//------------------------------人机/炮塔无限子弹------------------------------

void npcInfiniteAmmo(){

    //Chat.PrintToChat(all,"开始为NPC补充子弹");
    Schedule::Task( 5 , npcInfiniteAmmo );

    string survivor_riot = "survivor_riot";
	CBaseEntity @entRIOT = FindEntityByClassname( null, survivor_riot );
	while( entRIOT !is null)
	{
		NetProp::SetPropInt(entRIOT,"m_iTurretAmmo",NetProp::GetPropInt(entRIOT,"m_iMaxTurretAmmo"));
		@entRIOT = FindEntityByClassname( entRIOT, survivor_riot );
	}

    string survivor = "survivor";
	CBaseEntity @entSurvivor = FindEntityByClassname( null, survivor );
	while( entSurvivor !is null)
	{
		NetProp::SetPropInt(entSurvivor,"m_iTurretAmmo",NetProp::GetPropInt(entSurvivor,"m_iMaxTurretAmmo"));
		@entSurvivor = FindEntityByClassname( entSurvivor, survivor );
	}
	
	string survivor_turret = "survivor_turret";
	CBaseEntity @entTurret = FindEntityByClassname( null, survivor_turret );
	while( entTurret !is null)
	{
		NetProp::SetPropInt(entTurret,"m_iTurretAmmo",NetProp::GetPropInt(entTurret,"m_iMaxTurretAmmo"));
		@entTurret = FindEntityByClassname( entTurret, survivor_turret );
	}

}